package core.helpers;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.openqa.selenium.By;
import org.openqa.selenium.Platform;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.UnreachableBrowserException;
import org.openqa.selenium.safari.SafariDriver;
import org.testng.TestException;

import core.Config;
import core.Pilot;

public class WebDriverHelper extends DriverHelper
{

	private static int instanceCount = 0;
	/* Browser Tabs */
	private String windowHandler;
	private String handle;
	private String name;
	private String parentHandle;

	/**
	 * Initializes WebDriverHelper
	 * @param p_manager ApplicationManager instance
	 */
	public WebDriverHelper(Pilot p_manager)
	{
		super(p_manager);
	}

	/**
	 * Runs a local web browser
	 * @param p_browserName Browser name
	 * @param p_location Test location
	 */
	public boolean runLocalBrowser(String p_browserName, String p_location)
	{
		switch (pilot.dashboard().getServerType())
		{
			case Config.SERVER_LOCAL:
			{
				switch (pilot.dashboard().getOS())
				{
					default:
					{
						try
						{
							if (pilot.config().SCREENLOGGER_ENABLED)
							{
								// pilot.screenLogger().init();
							}

							driver = getBrowserInstance(p_browserName, p_location);

							// http://selenium.googlecode.com/git/docs/api/java/org/openqa/selenium/WebDriver.Timeouts.html
							driver.manage().timeouts().implicitlyWait(Config.WEBDRIVER_WAIT, TimeUnit.SECONDS);
							driver.manage().timeouts().pageLoadTimeout(Config.WEBDRIVER_WAIT, TimeUnit.SECONDS);
							driver.manage().timeouts().setScriptTimeout(Config.WEBDRIVER_WAIT, TimeUnit.SECONDS);
							// Browser tab handler
							windowHandler = pilot.driver().getDriver().getWindowHandle();

							// Maximize a browser window
							pilot.driver().getDriver().manage().window().maximize();
							// Proxy authentification
							if (Config.PROXY_AUTH && !pilot.dashboard().getProxy().equals("def"))
							{
								pilot.thread().start();
							}

							// Launch the ScreenLogger
							if (pilot.config().SCREENLOGGER_ENABLED)
							{
								// pilot.screenLogger().init();
								// pilot.screenLogger().start();
							}

							return true;

						}
						catch (Exception p_ex)
						{
							pilot.log().error("Cannot start a Web browser driver: " + p_ex);
							throw new TestException("Cannot start a Web browser driver: " + p_ex);

						}
					}
				}
			}
			case Config.SERVER_SAUCELABS:
			{
				this.runRemoteBrowser();
				pilot.log().info("Sauce Labs report: " + "https://saucelabs.com/tests/" + pilot.driver().getSessionId());
				return true;
			}
		}

		return false;
	}

	public boolean isBrowserOpen()
	{
		Boolean isOpen = (driver.toString().contains("(null)")) ? false : true;

		if (isOpen)
		{
			try
			{
				driver.getCurrentUrl();
			}
			catch (UnreachableBrowserException p_ex)
			{
				return false;
			}
		}
		return isOpen;
	}

	/**
	 * Runs a remote web browser on SauceLabs
	 * @param p_os OS name
	 * @param p_browser Browser name
	 * @param p_browserVersion Browser version
	 * @param p_proxy Proxy
	 * @param p_jobName Job name
	 */
	@SuppressWarnings("null")
	public void runRemoteBrowser(String p_os, String p_browser, String p_browserVersion, String p_proxy, String p_jobName)
	{
		DesiredCapabilities capabilities = null;

		// Set Browser Version
		if (p_browserVersion.equals("def"))
		{
			if (p_browser.equals(pilot.config().BROWSER_FIREFOX))
			{
				capabilities = getCapabilities(p_browser, p_proxy);
				capabilities.setCapability("version", pilot.config().BROWSER_FF_DEFAULT_VERSION);
				capabilities.setCapability("screen-resolution", pilot.config().SCREEN_RESOLUTION_1280x1024);
			}
			else if (p_browser.equals(pilot.config().BROWSER_CHROME))
			{
				capabilities = getCapabilities(p_browser, p_proxy);
				capabilities.setCapability("version", pilot.config().BROWSER_CH_DEFAULT_VERSION);
				capabilities.setCapability("screen-resolution", pilot.config().SCREEN_RESOLUTION_1280x1024);
			}
			else if (p_browser.equals(pilot.config().BROWSER_IEXPLORER))
			{
				capabilities = getCapabilities(p_browser, p_proxy);
				capabilities.setCapability("version", pilot.config().BROWSER_IE_DEFAULT_VERSION);
				capabilities.setCapability("screen-resolution", pilot.config().SCREEN_RESOLUTION_1280x1024);
			}
			else if (p_browser.equals(pilot.config().BROWSER_SAFARI))
			{
				//
			}
		}
		else
		{
			capabilities.setCapability("version", p_browserVersion);
		}

		// Set Job name
		capabilities.setCapability("name", p_jobName);

		// Set OS
		if (p_os.equals(pilot.config().OS_WIN_7))
		{
			capabilities.setCapability("platform", "Windows 7");
		}
		else if (p_os.equals(pilot.config().OS_WIN_8))
		{
			capabilities.setCapability("platform", Platform.WIN8);
		}
		else if (p_os.equals(pilot.config().OS_ANDROID))
		{
			capabilities.setCapability("platform", Platform.LINUX);
		}

		try
		{
			driver = new RemoteWebDriver(
					new URL("http://" + pilot.dashboard().getSaucelabsUsername() + ":" + pilot.dashboard().getSaucelabsKey()
							+ "@ondemand.saucelabs.com:80/wd/hub"),
					capabilities);
		}
		catch (Exception ex)
		{
			throw new TestException(ex.toString());
		}
	}

	/**
	 * Runs a remote browser on SauceLabs
	 */
	public void runRemoteBrowser()
	{
		runRemoteBrowser(pilot.dashboard().getOS(),
				pilot.dashboard().getBrowserName(),
				pilot.dashboard().getBrowserVersion(),
				pilot.dashboard().getProxy(),
				pilot.dashboard().getPlatform() + ":"
						+ pilot.dashboard().getTestName() + ":"
						+ pilot.dashboard().getSiteUrl() + ":"
						+ pilot.dashboard().getProxy() + ":"
						+ pilot.dashboard().getBrowserName() + "_"
						+ pilot.dashboard().getBrowserVersion());
	}

	/**
	 * Get desired capabilities
	 * @param p_browser Browser name
	 * @param p_proxy Browser proxy
	 * @return DesiredCapabilities instance
	 */
	@SuppressWarnings("static-access")
	private DesiredCapabilities getCapabilities(String p_browser, String p_proxy)
	{
		DesiredCapabilities capabilities = null;

		if (p_browser.equals(pilot.config().BROWSER_FIREFOX))
		{
			capabilities = getFirefoxOptions(p_proxy);
		}
		else if (p_browser.equals(pilot.config().BROWSER_CHROME))
		{
			capabilities = new DesiredCapabilities().chrome();
			capabilities.setBrowserName("Chrome");
		}
		else if (p_browser.equals(pilot.config().BROWSER_IEXPLORER))
		{
			capabilities = getIExplorerOptions(p_proxy);
		}
		else if (p_browser.equals(pilot.config().BROWSER_SAFARI))
		{
			capabilities = getSafariOptions(p_proxy);
		}

		return capabilities;
	}

	/**
	 * Gets Chrome options
	 * @param p_proxy Browser proxy
	 * @return ChromeOptions instance
	 */
	private ChromeOptions getChromeOptions(String p_proxy)
	{
		ChromeOptions options = new ChromeOptions();
		options.addArguments("--lang=en_US");

		if ((!p_proxy.equals("def") || p_proxy.isEmpty()))
		{
			options.addArguments("--proxy-server=" + p_proxy);
		}

		return options;
	}

	/**
	 * Gets Firefox options
	 * @param p_proxy Browser proxy
	 * @return DesiredCapabilities instance
	 */
	@SuppressWarnings("static-access")
	private DesiredCapabilities getFirefoxOptions(String p_proxy)
	{
		DesiredCapabilities capabilities = null;

		FirefoxProfile profile = new FirefoxProfile();
		profile.setPreference("intl.accept_languages", "en_US");
		capabilities = new DesiredCapabilities().firefox();
		capabilities.setBrowserName("Firefox");
		capabilities.setCapability(FirefoxDriver.PROFILE, profile);

		try
		{
			Runtime.getRuntime().exec("FOR /F \"tokens=5 delims= \" %P IN ('netstat -a -n -o ^| findstr :" + 7054 + "') DO TaskKill.exe /PID %P /T /F");
			wait(10);
		}
		catch (IOException e)
		{
			//
		}

		if ((!p_proxy.equals("def") || p_proxy.isEmpty()))
		{
			setProxyOptions(capabilities, p_proxy);
		}

		return capabilities;
	}

	/**
	 * Gets IExplorer options
	 * @param p_proxy Browser proxy
	 * @return DesiredCapabilities instance
	 */
	@SuppressWarnings("static-access")
	private DesiredCapabilities getIExplorerOptions(String p_proxy)
	{
		DesiredCapabilities capabilities = null;
		capabilities = new DesiredCapabilities().internetExplorer();
		capabilities.setBrowserName("iexplore");

		if ((!p_proxy.equals("def") || p_proxy.isEmpty()))
		{
			setProxyOptions(capabilities, p_proxy);
		}

		return capabilities;
	}

	/**
	 * Gets IExplorer options
	 * @param p_proxy Browser proxy
	 * @return DesiredCapabilities instance
	 */
	@SuppressWarnings("static-access")
	private DesiredCapabilities getSafariOptions(String p_proxy)
	{
		DesiredCapabilities capabilities = null;
		capabilities = new DesiredCapabilities().safari();
		capabilities.setBrowserName("Safari");
		return capabilities;
	}

	/**
	 * Gets Android options
	 * @param p_proxy Browser proxy
	 * @return DesiredCapabilities instance
	 */
	@SuppressWarnings("static-access")
	private DesiredCapabilities getAndroidOptions(String p_proxy)
	{
		DesiredCapabilities capabilities = null;
		capabilities = new DesiredCapabilities().android();
		capabilities.setBrowserName("Android");
		return capabilities;
	}

	private void setProxyOptions(DesiredCapabilities p_capabilities, String p_localProxy)
	{
		Proxy proxy = new Proxy();
		proxy.setHttpProxy(p_localProxy).
				setFtpProxy(p_localProxy).
				setSslProxy(p_localProxy);
		p_capabilities.setCapability("proxy", proxy);
	}

	/**
	 * Get instance of web driver
	 * @param p_browser Browser name
	 * @param p_proxy TODO
	 * @return Instance of browser
	 */
	public WebDriver getBrowserInstance(String p_browser, String p_proxy)
	{
		// Get browser instance
		if (p_browser.equals(pilot.config().BROWSER_FIREFOX))
		{
			return new FirefoxDriver(getFirefoxOptions(p_proxy));
		}
		else if (p_browser.equals(pilot.config().BROWSER_CHROME))
		{
			return new ChromeDriver(getChromeOptions(p_proxy));
		}
		else if (p_browser.equals(pilot.config().BROWSER_IEXPLORER))
		{
			return new InternetExplorerDriver(getIExplorerOptions(p_proxy));
		}
		else if (p_browser.equals(pilot.config().BROWSER_SAFARI))
		{
			return new SafariDriver(getSafariOptions(p_proxy));
		}
		else
		{
			throw new TestException("Browser is not supported => " + p_browser);
		}
	}

	/**
	 * Opens a new browser tab
	 * @param p_url
	 *            URL for opening
	 */
	public void addTab(String p_url)
	{
		try
		{
			pilot.log().debug("Open a new browser tab: " + p_url);
			parentHandle = pilot.driver().getWindowHandle();
			name = createUniqueName();
			handle = createWindow(p_url);

			// Switch to that window and load the URL to wait
			switchToWindow().get(p_url);
		}
		catch (Exception ex)
		{
			pilot.log().warn("Cannot open a new browser tab: " + p_url);
		}
	}

	/**
	 * Switches to first browser tab
	 */
	public void switchToFirstTab()
	{

		try
		{
			pilot.log().debug("Switch to the first browser tab");
			pilot.driver().switchToWindow(windowHandler);
			pilot.driver().switchToActiveElement();
		}
		catch (Exception ex)
		{
			pilot.log().warn("Cannot switch to the first browser tab");
		}
	}

	/**
	 * Switches to the second browser tab
	 */
	public void switchToSecondTab()
	{
		try
		{
			pilot.log().debug("Switch to the second browser tab");

			for (String handle : pilot.driver().getWindowHandles())
			{
				if (handle != windowHandler)
				{
					pilot.driver().switchToWindow(handle);
					pilot.driver().switchToActiveElement();
				}
			}
		}
		catch (Exception ex)
		{
			pilot.log().warn("Cannot switch to the second browser tab");
		}
	}

	/**
	 * Checks for is child windows
	 */
	public boolean isChildWindows()
	{
		Set<String> winSet = pilot.driver().getWindowHandles();
		List<String> winList = new ArrayList<String>(winSet);

		if (winList.size() > 1)
		{
			pilot.log().debug("Check for is child browser windows: " + true);
			return true;
		}
		else
		{
			pilot.log().debug("Check for is child browser windows: " + false);
			return false;
		}
	}

	/**
	 * Closes all child windows
	 */
	public boolean closeAllChildWindows()
	{
		pilot.log().info("Close child browser windows");
		String parentWindow = pilot.driver().getWindowHandle();
		Set<String> winSet = pilot.driver().getWindowHandles();
		List<String> winList = new ArrayList<String>(winSet);
		String newTab = null;

		if (isChildWindows())
		{
			if (winList.size() > 1)
			{
				newTab = winList.get(winList.size() - 1);
				pilot.driver().switchToWindow(newTab);
				pilot.driver().close();
			}

			pilot.driver().switchToWindow(parentWindow);
			pilot.log().debug("Child browser windows closed");

			return true;
		}
		else
		{
			pilot.log().debug("Cannot close child browser window because they are absent");

			return false;
		}
	}

	/**
	 * Gets the parent browser window Handle
	 * @return Parent window handle
	 */
	public String getParentHandle()
	{
		pilot.log().debug("Get the parent window handlem: " + parentHandle);
		return parentHandle;
	}

	/**
	 * Closes a browser window
	 */
	public void closeWindow()
	{
		pilot.log().debug("Close the current browser tab and switch back to the parent window hahdle: " + parentHandle);
		switchToWindow().close();
		handle = "";
		pilot.driver().switchToWindow(parentHandle);
	}

	/**
	 * Creates a unique name of browser window
	 * @return String
	 */
	private String createUniqueName()
	{
		String nameOfBrowserWindow = "Web_Window_" + instanceCount++;
		pilot.log().debug("Create a unique name of browser window: " + nameOfBrowserWindow);

		return nameOfBrowserWindow;
	}

	/**
	 * Switches to the window by handle
	 * @return WebDriver instance
	 */
	public WebDriver switchToWindow()
	{
		checkForIsBrowserWindowClosed();
		pilot.log().debug("Switch to the window: " + handle);

		return pilot.driver().switchToWindow(handle);
	}

	/**
	 * Switches to the parent window
	 * @return WebDriver instance
	 */
	public WebDriver switchToParent()
	{
		checkForIsBrowserWindowClosed();
		pilot.log().debug("Switch to the parent window: " + parentHandle);

		return pilot.driver().switchToWindow(parentHandle);
	}

	/**
	 * Creates a new window
	 * @param p_url
	 *            URL for new window
	 * @return WIndow handle
	 */
	private String createWindow(String p_url)
	{
		pilot.log().debug("Create a new browser window");

		// Record old handles
		Set<String> oldHandles = pilot.driver().getWindowHandles();
		parentHandle = pilot.driver().getWindowHandle();
		// Inject an anchor element
		pilot.js().execute(injectAnchorTag(name, p_url));
		// Click on the anchor element
		pilot.driver().click(By.id(name));
		handle = getNewHandle(oldHandles);
		pilot.log().debug("New browser window is created: " + handle);

		return handle;
	}

	/**
	 * Gets Handle of current window
	 * @param p_oldHandles
	 *            Old windows handle
	 * @return Window handle
	 */
	private String getNewHandle(Set<String> p_oldHandles)
	{

		Set<String> newHandles = pilot.driver().getWindowHandles();
		newHandles.removeAll(p_oldHandles);

		// Find the new window
		for (String handle : newHandles)
		{
			pilot.log().debug("Get a handle of browser window: " + handle);
			return handle;
		}

		pilot.log().debug("Get a handle of browser window: " + null);
		return null;
	}

	/**
	 * Checks for is window closed
	 */
	private boolean checkForIsBrowserWindowClosed()
	{
		if (handle == null || handle.equals(""))
		{
			pilot.log().debug("Check for is browser window closed: " + true);

			return true;
		}

		pilot.log().debug("Check for is browser window closed: " + false);

		return false;
	}

	/**
	 * Injects an anchor tag
	 * @param p_id
	 * @param p_url
	 * @return Anchor tag
	 */
	private String injectAnchorTag(String p_id, String p_url)
	{
		pilot.log().debug("Inject an anchor tag");

		return String.format("var anchorTag = document.createElement('a'); " +
				"anchorTag.appendChild(document.createTextNode('nwh'));" +
				"anchorTag.setAttribute('id', '%s');" +
				"anchorTag.setAttribute('href', '%s');" +
				"anchorTag.setAttribute('target', '_blank');" +
				"anchorTag.setAttribute('style', 'display:block;');" +
				"document.getElementsByTagName('body')[0].appendChild(anchorTag);",
				p_id, p_url
				);
	}

	/**
	 * Waits web element for visibility
	 * @param p_locatorOfElement
	 * @return true - element is visible, false - element is not visible
	 */
	public boolean waitForVisible(By p_locatorOfElement)
	{
		try
		{
			super.waitForVisibility(p_locatorOfElement, null, true);
			pilot.log().info("Check for visibility: " + p_locatorOfElement + " => " + true);
			return true;
		}
		catch (Exception p_ex)
		{
			pilot.log().warn("Check for visibility: " + p_locatorOfElement + " => " + false + "\n" + p_ex.toString());
			return false;
		}
	}

	/**
	 * Waits web element for invisibility
	 * @param p_locatorOfElement
	 * @return true - element is invisible, false - element is visible
	 */
	public boolean waitForInvisible(By p_locatorOfElement)
	{
		try
		{
			super.waitForInvisibility(p_locatorOfElement, null, true);
			pilot.log().info("Check for invisibility: " + p_locatorOfElement + " => " + true);
			return true;
		}
		catch (Exception p_ex)
		{
			pilot.log().warn("Check for invisibility: " + p_locatorOfElement + " => " + false + "\n" + p_ex.toString());
			return false;
		}
	}
}